﻿/* Copyright (C) 2008 - 2011 by John Cronin
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace tysila
{
    class Program
    {
        public const string DefaultAssembler = "x86_64";
        public const string DefaultOFormat = "elf64";

        static string output_header = null;
        public static string input_file = null;
        static string output_file = null;
        static string unimplemented = null;
        public static string assembler = DefaultAssembler;
        public static string oformat = DefaultOFormat;
        static string output_cinit = null;
        static string irdump = null;
		static int debug_level = 0;
        static bool interactive = false;
        static bool quiet = false;
        static bool requested_module_only = false;
        static bool whole_module = false;
        static bool packed_structs = true;
        static bool profile = false;
        static List<string> extra_types = new List<string>();
        static List<string> extra_methods = new List<string>();
        static List<string> extra_typeinfos = new List<string>();
        static List<string> exclude_files = new List<string>();

        /* Boiler plate */
        const string version = "0.2.0";
        const string year = "2009,2010,2011";
        const string authors = "John Cronin";
        const string website = "http://tysos.googlecode.com";
        const string nl = "\n";
        public const string bplate = "tysila " + version + " (" + website + ")" + nl +
            "Copyright (C) " + year + " " + authors + nl +
            "This is free software.  Please see the source for copying conditions.  There is no warranty, " +
            "not even for merchantability or fitness for a particular purpose";

        public static List<string> search_dirs = new List<string> { "", ".", Program.DirectoryDelimiter };

		public static string DirectoryDelimiter
		{
			get
			{
				if(Environment.OSVersion.Platform == PlatformID.Unix)
					return "/";
				else
					return "\\";
			}
		}

        public static Assembler LoadAssembler(string assembler)
        {
            try
            {
                Type asst = Type.GetType("tysila." + assembler + "_Assembler", false, true);
                System.Reflection.ConstructorInfo ctorm = asst.GetConstructor(new Type[] { });
                if (ctorm == null)
                    throw new TypeLoadException();
                return ctorm.Invoke(new object[] { }) as Assembler;
            }
            catch (Exception e)
            {
                if (e is TypeLoadException)
                {
                    Console.WriteLine(assembler + " is not a valid assembler");
                    Console.WriteLine();
                    return null;
                }
                throw;
            }
        }

        static int Main(string[] args)
        {
            if (ParseArgs(args) == false)
            {
                ShowHelp();
                return -1;
            }
            
            Assembler ass = LoadAssembler(assembler);
            ass.profile = profile;
            if (output_file == null)
                ass.output_name = "";
            else
            {
                FileInfo fi = new FileInfo(output_file);
                string fname = fi.Name;
                fname = fname.Substring(0, fname.Length - fi.Extension.Length);
                ass.output_name = fname;
            }

            if (ass == null)
            {
                ShowHelp();
                return -1;
            }

            Metadata m = Metadata.LoadAssembly(input_file, ass, ass.output_name);
            File f = m.File;
            
            if (irdump != null)
                ass.ProduceIrDump = true;

            ass.AddAssembly(m);

            if (interactive)
            {
                Interactive i = new Interactive(ass);
                if (i.Run() == Interactive.InteractiveReturn.Quit)
                    return 0;
            }

#if !DEBUG
            try
            {
#endif
            if (output_file != null)
            {
                /* Find the requested output module */
                IOutputFile ew = null;
                Type[] ass_types = typeof(Assembler).Assembly.GetTypes();
                foreach (Type t in ass_types)
                {
                    if (t.Name.ToLower() == (oformat + "Writer").ToLower())
                    {
                        System.Reflection.ConstructorInfo ctorm2 = t.GetConstructor(new Type[] { typeof(string) });
                        ew = ctorm2.Invoke(new object[] { input_file }) as IOutputFile;
                        break;
                    }
                }
                if (ew == null)
                {
                    ShowHelp();
                    return -1;
                }

                /* Add in all symbols specified in the files referenced on the --exclude command line to the Requestor
                 * implying they have already been compiled and do not need to be done so again */
                foreach (string exclude_file in exclude_files)
                {
                    StreamReader sr = new StreamReader(exclude_file);

                    while (!sr.EndOfStream)
                    {
                        string mangled_name = sr.ReadLine();
                        if (mangled_name.StartsWith("_"))
                        {
                            Mangler2.ObjectToMangle obj = Mangler2.DemangleName(mangled_name, ass);

                            switch (obj.ObjectType)
                            {
                                case Mangler2.ObjectToMangle.ObjectTypeType.FieldInfo:
                                    ass.Requestor.ExcludeFieldInfo(Mangler2.GetField(obj, ass));
                                    break;
                                case Mangler2.ObjectToMangle.ObjectTypeType.Method:
                                    ass.Requestor.ExcludeMethod(Mangler2.GetMethod(obj, ass));
                                    break;
                                case Mangler2.ObjectToMangle.ObjectTypeType.MethodInfo:
                                    ass.Requestor.ExcludeMethodInfo(Mangler2.GetMethod(obj, ass));
                                    break;
                                case Mangler2.ObjectToMangle.ObjectTypeType.VirtualTable:
                                    ass.Requestor.ExcludeType(Mangler2.GetType(obj, ass));
                                    break;
                                case Mangler2.ObjectToMangle.ObjectTypeType.TypeInfo:
                                    ass.Requestor.ExcludeTypeInfo(Mangler2.GetType(obj, ass));
                                    break;
                            }
                        }
                    }

                    sr.Close();
                }

                // Limit to only compiling types in the assembly listed on the command line, if requested
                if (requested_module_only)
                    ass.Requestor.ModulesToAssemble.Add(m);

                /* If whole-module is specified then we need to compile all non-generic types, and
                 *  all non-generic modules within those types.
                 *  
                 * In addition, we need to add each string contained in _all_ modules to the StringTable */

                // As each table row is unique, and the requested method list is empty, skip checking for duplicates in Requestor
                //ass.Requestor.skip_checks = true;
                if (whole_module)
                {
                    List<Metadata.TypeDefRow> non_generic_types = new List<Metadata.TypeDefRow>();
                    List<Metadata.MethodDefRow> non_generic_meths = new List<Metadata.MethodDefRow>();
                    foreach (Metadata.TypeDefRow tdr in m.Tables[(int)Metadata.TableId.TypeDef])
                    {
                        if (!tdr.IsGeneric)
                        {
                            non_generic_types.Add(tdr);
                            Assembler.TypeToCompile ttc = new Assembler.TypeToCompile(tdr, ass);
                            ass.Requestor.RequestType(ttc);
                            ass.Requestor.RequestTypeInfo(ttc);

                            for (Metadata.TableIndex ti = tdr.MethodList; ti < Metadata.GetLastMethod(tdr); ti++)
                            {
                                if (ti.TableId == Metadata.TableId.MethodDef)
                                {
                                    Metadata.MethodDefRow mdr = ti.Value as Metadata.MethodDefRow;
                                    if ((mdr.Signature[0] & 0x10) == 0)
                                    {
                                        non_generic_meths.Add(mdr);
                                        //Signature.BaseMethod bmeth = Signature.ParseMethodDefSig(m, mdr.Signature);
                                        Signature.BaseMethod bmeth = mdr.GetSignature();
                                        //Assembler.TypeToCompile ttc = new Assembler.TypeToCompile(mdr.owning_type, ass);
                                        Assembler.MethodToCompile mtc = new Assembler.MethodToCompile { _ass = ass, meth = mdr, msig = bmeth, tsigp = ttc.tsig, type = ttc.type };
                                        ass.Requestor.RequestMethod(mtc);
                                        ass.Requestor.RequestMethodInfo(mtc);
                                    }
                                }
                            }

                            for (Metadata.TableIndex fi = tdr.FieldList; fi < Metadata.GetLastField(m, tdr); fi++)
                            {
                                if (fi.TableId == Metadata.TableId.Field)
                                {
                                    Metadata.FieldRow fr = fi.Value as Metadata.FieldRow;

                                    //Signature.Field fsig = Signature.ParseFieldSig(m, fr.Signature);
                                    Signature.Field fsig = fr.GetSignature();
                                    if (!((fsig.Type is Signature.GenericParam) || (fsig.Type is Signature.GenericType) || (fsig.Type is Signature.GenericMethodParam)))
                                    {
                                        Assembler.FieldToCompile ftc = new Assembler.FieldToCompile
                                        {
                                            _ass = ass,
                                            field = fr,
                                            fsig = fsig,
                                            definedin_tsig = ttc.tsig,
                                            definedin_type = ttc.type
                                        };
                                        ass.Requestor.RequestFieldInfo(ftc);
                                    }
                                }


                            }
                        }
                    }

                    // Add in strings for the non compiled methods
                    IEnumerable<Metadata.MethodDefRow> non_compiled_meths = util.Except<Metadata.MethodDefRow>((Metadata.MethodDefRow[])m.Tables[(int)Metadata.TableId.MethodDef], non_generic_meths);
                    foreach (Metadata.MethodDefRow ncm in non_compiled_meths)
                    {
                        byte[] impl = ncm.Body.Body;
                        if (impl != null)
                        {
                            List<Assembler.cfg_node> nodes = ass.BuildControlGraph(impl, m, new Assembler.AssemblerState());
                            foreach (Assembler.cfg_node node in nodes)
                            {
                                foreach (Assembler.InstructionLine inst in node.instrs)
                                {
                                    if (inst.opcode.opcode1 == Assembler.SingleOpcodes.ldstr)
                                        m.StringTable.GetStringAddress(((Metadata.UserStringHeapItem)inst.inline_tok.Value).Value, ass);
                                }
                            }
                        }
                    }
                }
                // Turn duplicate checks back on
                ass.Requestor.skip_checks = false;

                // Clear the ModulesToAssemble list - some extra ones may be specified in other modules
                ass.Requestor.ModulesToAssemble.Clear();

                // Add in extra types, methods and typeinfos requested on the command line
                foreach (string e in extra_types)
                {
                    Assembler.TypeToCompile ttc = Mangler2.DemangleType(e, ass);
                    ass.Requestor.RequestType(ttc);
                    ass.Requestor.RequestTypeInfo(ttc);

                    Layout l = Layout.GetLayout(ttc, ass);
                    foreach (Layout.Method vm in l.VirtualMethods)
                    {
                        if (vm.implementation_mtc.HasValue)
                            ass.Requestor.RequestMethod(vm.implementation_mtc.Value);
                    }
                }
                foreach (string e in extra_typeinfos)
                {
                    Assembler.TypeToCompile ttc = Mangler2.DemangleType(e, ass);
                    ass.Requestor.RequestTypeInfo(ttc);
                }
                foreach (string e in extra_methods)
                {
                    Assembler.MethodToCompile mtc = Mangler2.DemangleMethod(e, ass);
                    ass.Requestor.RequestMethod(mtc);
                }

                // Limit to only compiling types in the assembly listed on the command line, if requested
                if (requested_module_only)
                    ass.Requestor.ModulesToAssemble.Add(m);

                // Find the entry point
                if (f.GetStartToken() != 0x0)
                {
                    Token ept = new Token(f.GetStartToken(), m);
                    Metadata.MethodDefRow mdr = ept.Value as Metadata.MethodDefRow;
                    Metadata.TypeDefRow tdr = Metadata.GetOwningType(mdr.m, mdr);
                    Assembler.MethodToCompile start_point = new Assembler.MethodToCompile
                    {
                        _ass = ass,
                        meth = mdr,
                        msig = Signature.ParseMethodDefSig(m, mdr.Signature, ass),
                        type = tdr,
                        tsigp = new Signature.Param(new Token(tdr), ass)
                    };
                    ass.Requestor.RequestMethod(start_point);
                    ew.SetEntryPoint(Mangler2.MangleMethod(start_point, ass));
                }

                // Identify those methods with the always compile attribute
                foreach (Metadata.CustomAttributeRow car in m.Tables[(int)Metadata.TableId.CustomAttribute])
                {
                    Assembler.MethodToCompile camtc = Metadata.GetMTC(car.Type, new Assembler.TypeToCompile(), null, ass);

                    string caname = Mangler2.MangleMethod(camtc, ass);
                    if (caname == "_ZN5tysos3ABI22AlwaysCompileAttributeM_0_7#2Ector_Rv_P1u1t")
                    {
                        Metadata.MethodDefRow mdr = Metadata.GetMethodDef(car.Parent.ToToken(), ass);
                        Metadata.TypeDefRow tdr = Metadata.GetOwningType(mdr.m, mdr);
                        ass.Requestor.RequestMethod(new Assembler.MethodToCompile
                        {
                            _ass = ass,
                            meth = mdr,
                            msig = Signature.ParseMethodDefSig(m, mdr.Signature, ass),
                            type = tdr,
                            tsigp = new Signature.Param(new Token(tdr), ass)
                        });
                    }
                }

                // Do the actual compilation
                Assembler.IDoCompileFeedback feedback = null;
                if (!quiet)
                    feedback = new Interactive(ass);
                List<string> unimplemented_list = null;
                if (unimplemented != null)
                    unimplemented_list = new List<string>();
                ass.DoCompile(ew, unimplemented_list, feedback);

                // Add the string tables for the requested modules
                foreach (Assembler.AssemblyInformation ai in ass.GetLoadedAssemblies())
                    ai.m.StringTable.WriteToOutput(ew, ass);

                // Write the output file
                if (!quiet)
                    new Interactive(ass).DisplayLine("Writing output file: " + output_file);
                FileStream oput = new FileStream(output_file, FileMode.Create, FileAccess.Write);
                ew.Write(oput);
                oput.Close();

                // Write the unimplemented list
                if (unimplemented != null)
                {
                    if (!quiet)
                        new Interactive(ass).DisplayLine("Writing list of unimplemented internal calls: " + unimplemented);
                    FileStream u_oput = new FileStream(unimplemented, FileMode.Create, FileAccess.Write);
                    StreamWriter u_sw = new StreamWriter(u_oput);
                    foreach (string str in unimplemented_list)
                        u_sw.WriteLine(str);
                    u_sw.Close();
                }
            }
#if !DEBUG
            }
            catch (Exception e)
            {
                Console.WriteLine("Error: " + e.Message);
				if(debug_level >= 1)
				{
					Console.WriteLine("Exception object: " + e.GetType().ToString());
					Console.WriteLine("Stack trace: \n{0}", e.StackTrace);
				}
                return -1;
            }
#endif

            if ((output_cinit != null) && (output_header == null))
            {
                if (output_cinit.Contains(".c"))
                    output_header = output_cinit.Substring(0, output_cinit.LastIndexOf(".c")) + ".h";
                else
                    output_header = output_cinit + ".h";
            }
            if (output_header != null)
                WriteHeader(m, ass);

            if (irdump != null)
            {
                StreamWriter idsw = new StreamWriter(new FileStream(irdump, FileMode.Create, FileAccess.Write));
                idsw.Write(ass.IrDump);
                idsw.Close();
            }

            return 0;
        }

        private static void WriteHeader(Metadata m, Assembler ass)
        {
            FileStream fs = new FileStream(output_header, FileMode.Create, FileAccess.Write);
            StreamWriter sw = new StreamWriter(fs);

            StreamWriter hmsw = new StreamWriter(new MemoryStream());
            StreamWriter cmsw = new StreamWriter(new MemoryStream());

            StreamWriter oci = null;
            if (output_cinit != null)
            {
                oci = new StreamWriter(new FileStream(output_cinit, FileMode.Create, FileAccess.Write));
                oci.WriteLine("#include \"" + output_header + "\"");
                oci.WriteLine("#include <string.h>");
                oci.WriteLine("#include <malloc.h>");
                oci.WriteLine("#include <stdint.h>");
                oci.WriteLine();
                oci.WriteLine("uint64_t Get_Symbol_Addr(const char *name);");
                oci.WriteLine();
                oci.WriteLine("static int32_t next_obj_id = -1;");
                oci.WriteLine();
            }

            List<string> advance_defines = new List<string>();
            List<string> external_defines = new List<string>();
            List<string> func_headers = new List<string>();

            EmitType(Metadata.GetTypeDef("mscorlib", "System", "Object", ass), hmsw, cmsw, advance_defines,
                external_defines, func_headers, ass);
            EmitType(Metadata.GetTypeDef("mscorlib", "System", "String", ass), hmsw, cmsw, advance_defines,
                external_defines, func_headers, ass);
            EmitArrayInit(hmsw, cmsw, func_headers, ass);

            foreach (Metadata.CustomAttributeRow car in m.Tables[(int)Metadata.TableId.CustomAttribute])
            {
                Assembler.MethodToCompile camtc = Metadata.GetMTC(car.Type, new Assembler.TypeToCompile(), null, ass);

                //Mangler.IncludeModule = false;
                string caname = Mangler2.MangleMethod(camtc, ass);
                //Mangler.IncludeModule = true;
                if (caname == "_ZN5tysos3ABI22OutputCHeaderAttributeM_0_7#2Ector_Rv_P1u1t")
                {
                    Metadata.TypeDefRow tdr = Metadata.GetTypeDef(car.Parent.ToToken(), ass);
                    EmitType(tdr, hmsw, cmsw, advance_defines, external_defines, func_headers, ass);
                }
            }

            sw.WriteLine("#include <stdint.h>");
            sw.WriteLine();
            EmitArrayType(sw, ass);
            foreach (string s in advance_defines)
                sw.WriteLine(s);
            sw.WriteLine();
            if (oci != null)
            {
                foreach (string s2 in func_headers)
                    sw.WriteLine(s2);
                sw.WriteLine();
            }
            hmsw.Flush();
            StreamReader hmsr = new StreamReader(hmsw.BaseStream);
            hmsr.BaseStream.Seek(0, SeekOrigin.Begin);
            string hs = hmsr.ReadLine();
            while (hs != null)
            {
                sw.WriteLine(hs);
                hs = hmsr.ReadLine();
            }

            sw.Close();

            if (oci != null)
            {
                foreach (string s in external_defines)
                    oci.WriteLine(s);
                oci.WriteLine();

                cmsw.Flush();
                StreamReader cmsr = new StreamReader(cmsw.BaseStream);
                cmsr.BaseStream.Seek(0, SeekOrigin.Begin);
                string cs = cmsr.ReadLine();
                while (cs != null)
                {
                    oci.WriteLine(cs);
                    cs = cmsr.ReadLine();
                }
                oci.Close();
            }
        }

        private static void EmitType(Metadata.TypeDefRow tdr, StreamWriter hmsw, StreamWriter cmsw,
            List<string> advance_defines, List<string> external_defines, List<string> header_funcs, Assembler ass)
        {
            Layout l = Layout.GetLayout(new Assembler.TypeToCompile { _ass = ass, tsig = new Signature.Param(tdr, ass), type = tdr }, ass);
            //Layout l = tdr.GetLayout(new Signature.Param(new Token(tdr), ass).Type, ass, null);

            if (!tdr.IsEnum(ass))
            {
                hmsw.WriteLine("struct " + tdr.TypeNamespace + "_" + tdr.TypeName + " {");
                advance_defines.Add("struct " + tdr.TypeNamespace + "_" + tdr.TypeName + ";");
                foreach (Layout.Field iif in l.InstanceFields)
                    hmsw.WriteLine("    " + ass.GetCType(iif.field.fsig) + " " + iif.field.field.Name + ";");
                if (packed_structs)
                    hmsw.WriteLine("} __attribute__((__packed__));");
                else
                    hmsw.WriteLine("};");
            }
            else
            {
                // Identify underlying type
                string u_type = ass.GetUnderlyingCType(tdr);
                bool needs_comma = false;
                hmsw.WriteLine("enum " + tdr.TypeNamespace + "_" + tdr.TypeName + " {");
                for (Metadata.TableIndex ti = tdr.FieldList; ti < Metadata.GetLastField(tdr.m, tdr); ti++)
                {
                    Metadata.FieldRow vfr = ti.Value as Metadata.FieldRow;
                    if ((vfr.Flags & 0x8050) == 0x8050)
                    {
                        foreach (Metadata.ConstantRow cr in tdr.m.Tables[(int)Metadata.TableId.Constant])
                        {
                            if (cr.Parent.Value == vfr)
                            {
                                int val = ass.FromByteArrayI4(cr.Value);
                                if (needs_comma)
                                    hmsw.WriteLine(",");
                                hmsw.Write("    " + vfr.Name + " = " + val.ToString());
                                needs_comma = true;
                            }
                        }
                    }
                }
                hmsw.WriteLine();
                hmsw.WriteLine("};");
            }

            hmsw.WriteLine();

            if (output_cinit != null)
            {
                if (!tdr.IsValueType(ass))
                {
                    string init_func = "void Init_" + tdr.TypeNamespace + "_" + tdr.TypeName + "(struct " +
                        tdr.TypeNamespace + "_" + tdr.TypeName + " *obj)";
                    cmsw.WriteLine(init_func);
                    header_funcs.Add(init_func + ";");
                    cmsw.WriteLine("{");

                    if (l.vtbl_name != null)
                        cmsw.WriteLine("    obj->__vtbl = Get_Symbol_Addr(\"" + l.vtbl_name + "\");");
                    if (l.has_obj_id)
                        cmsw.WriteLine("    obj->__object_id = next_obj_id--;");

                    cmsw.WriteLine("}");
                    cmsw.WriteLine();

                    if (tdr.TypeFullName == "System.String")
                        EmitStringInit(tdr, hmsw, cmsw, advance_defines, external_defines, header_funcs, ass);
                }
            }
        }

        private static void EmitArrayInit(StreamWriter hmsw, StreamWriter cmsw, List<string> header_funcs, 
            Assembler ass)
        {
            EmitArrayInit(BaseType_Type.Object, "Ref", hmsw, cmsw, header_funcs, ass);
            EmitArrayInit(BaseType_Type.Byte, "Byte", hmsw, cmsw, header_funcs, ass);
            EmitArrayInit(BaseType_Type.Char, "Char", hmsw, cmsw, header_funcs, ass);
            EmitArrayInit(BaseType_Type.I, "I", hmsw, cmsw, header_funcs, ass);
            EmitArrayInit(BaseType_Type.I1, "I1", hmsw, cmsw, header_funcs, ass);
            EmitArrayInit(BaseType_Type.I2, "I2", hmsw, cmsw, header_funcs, ass);
            EmitArrayInit(BaseType_Type.I4, "I4", hmsw, cmsw, header_funcs, ass);
            EmitArrayInit(BaseType_Type.I8, "I8", hmsw, cmsw, header_funcs, ass);
            EmitArrayInit(BaseType_Type.U, "U", hmsw, cmsw, header_funcs, ass);
            EmitArrayInit(BaseType_Type.U1, "U1", hmsw, cmsw, header_funcs, ass);
            EmitArrayInit(BaseType_Type.U2, "U2", hmsw, cmsw, header_funcs, ass);
            EmitArrayInit(BaseType_Type.U4, "U4", hmsw, cmsw, header_funcs, ass);
            EmitArrayInit(BaseType_Type.U8, "U8", hmsw, cmsw, header_funcs, ass);
        }

        private static void EmitArrayType(StreamWriter hmsw, Assembler ass)
        {
            hmsw.WriteLine("struct __array");
            hmsw.WriteLine("{");
            hmsw.WriteLine("    " + ass.GetCType(BaseType_Type.I) + "          __vtbl;");
            hmsw.WriteLine("    int32_t          __object_id;");
            hmsw.WriteLine("    int32_t          __mutex_lock;");
            hmsw.WriteLine("    int32_t          rank;");
            hmsw.WriteLine("    int32_t          elem_size;");
            hmsw.WriteLine("    int32_t          inner_array_length;");
            hmsw.WriteLine("    " + ass.GetCType(BaseType_Type.I) + "          elemtype;");
            hmsw.WriteLine("    " + ass.GetCType(BaseType_Type.I) + "          lobounds;");
            hmsw.WriteLine("    " + ass.GetCType(BaseType_Type.I) + "          sizes;");
            hmsw.WriteLine("    " + ass.GetCType(BaseType_Type.I) + "          inner_array;");
            if (packed_structs)
                hmsw.WriteLine("} __attribute__((__packed__));");
            else
                hmsw.WriteLine("};");
            hmsw.WriteLine();
        }

        private static void EmitArrayInit(BaseType_Type baseType_Type, string tname, StreamWriter hmsw, StreamWriter cmsw,
            List<string> header_funcs, Assembler ass)
        {
            string typestr = ass.GetCType(baseType_Type);
            string init_func_name = "void Create_" + tname + "_Array(struct __array **arr_obj)";

            /* Arrays have four pieces of memory allocated:
             * - The array superblock
             * - The lobounds array
             * - The sizes array
             * - The actual array data
             * 
             * We do not allocate the last 3, as they may need to be placed at a different virtual address
             * when relocated - let the implementation decide this
             * 
             * Code is:
             * 
             * struct __array
             * {
             *     intptr           __vtbl;
             *     int32_t          __object_id;
             *     int32_t          __mutex_lock;
             *     int32_t          rank;
             *     int32_t          elem_size;
             *     intptr           lobounds;
             *     intptr           sizes;
             *     intptr           inner_array;
             * } __attribute__((__packed__));
             * 
             * void Create_X_Array(__array **arr_obj, int32_t num_elems)
             * {
             *     *arr_obj = (__array *)malloc(sizeof(arr_obj));
             *     (*arr_obj)->rank = 1;
             * }
             */

            int elem_size = ass.GetPackedSizeOf(new Signature.Param(baseType_Type));

            header_funcs.Add(init_func_name + ";");
            cmsw.WriteLine(init_func_name);
            cmsw.WriteLine("{");
            cmsw.WriteLine("    *arr_obj = (struct __array *)malloc(sizeof(struct __array));");
            cmsw.WriteLine("    (*arr_obj)->__object_id = next_obj_id--;");
            cmsw.WriteLine("    (*arr_obj)->rank = 1;");
            cmsw.WriteLine("    (*arr_obj)->elem_size = sizeof(" + typestr + ");");
            cmsw.WriteLine("}");
            cmsw.WriteLine();
        }

        private static void EmitStringInit(Metadata.TypeDefRow tdr, StreamWriter hmsw, StreamWriter cmsw,
            List<string> advance_defines, List<string> external_defines, List<string> header_funcs, Assembler ass)
        {
            // Emit a string creation instruction of the form:
            // void CreateString(System_String **obj, const char *s)

            string init_func = "void CreateString(struct System_String **obj, const char *s)";
            header_funcs.Add(init_func + ";");

            cmsw.WriteLine(init_func);
            cmsw.WriteLine("{");
            cmsw.WriteLine("    int l = strlen(s);");
            cmsw.WriteLine("    int i;");
            cmsw.WriteLine("    " + ass.GetCType(BaseType_Type.Char) + " *p;");
            cmsw.WriteLine("    *obj = (struct System_String *)malloc(sizeof(struct System_String) + l * sizeof(" +
                ass.GetCType(BaseType_Type.Char) + "));");
            cmsw.WriteLine("    Init_System_String(*obj);");
            cmsw.WriteLine("    (*obj)->length = l;");
            cmsw.WriteLine("    p = &((*obj)->start_char);");
            //cmsw.WriteLine("    p = (" + ass.GetCType(BaseType_Type.Char) +
            //    " *)(*obj + sizeof(struct System_String));");
            cmsw.WriteLine("    for(i = 0; i < l; i++)");
            cmsw.WriteLine("        p[i] = (" + ass.GetCType(BaseType_Type.Char) + ")s[i];");
            cmsw.WriteLine("}");
            cmsw.WriteLine();
        }

        private static bool ParseArgs(string[] args)
        {
            int i = 0;
            while (i < args.Length)
            {
                if (args[i] == "--oformat")
                {
                    i++;
                    if ((i >= args.Length) || (args[i].StartsWith("-")))
                        return false;
                    oformat = args[i];
                }
                else if (args[i] == "-o")
                {
                    i++;
                    if ((i >= args.Length) || (args[i].StartsWith("-")))
                        return false;
                    output_file = args[i];
                }
				else if (args[i] == "-d")
				{
					debug_level++;
				}
                else if (args[i] == "--assembler")
                {
                    i++;
                    if ((i >= args.Length) || (args[i].StartsWith("-")))
                        return false;
                    output_file = args[i];
                }
                else if (args[i] == "--output-header")
                {
                    i++;
                    if ((i >= args.Length) || (args[i].StartsWith("-")))
                        return false;
                    output_header = args[i];
                }
                else if (args[i] == "--output-cinit")
                {
                    i++;
                    if ((i >= args.Length) || (args[i].StartsWith("-")))
                        return false;
                    output_cinit = args[i];
                }
                else if (args[i] == "--irdump")
                {
                    i++;
                    if ((i >= args.Length) || (args[i].StartsWith("-")))
                        return false;
                    irdump = args[i];
                }
                else if (args[i] == "--exclude")
                {
                    i++;
                    if ((i >= args.Length) || (args[i].StartsWith("-")))
                        return false;
                    exclude_files.Add(args[i]);
                }
                else if (args[i] == "--unimplemented")
                {
                    i++;
                    if ((i >= args.Length) || (args[i].StartsWith("-")))
                        return false;
                    unimplemented = args[i];
                }
                else if (args[i] == "--requested-module-only")
                {
                    requested_module_only = true;
                }
                else if (args[i].StartsWith("-L"))
                {
                    if (args[i].Length > 2)
                        Program.search_dirs.Add(args[i].Substring(2));
                    else
                    {
                        i++;
                        Program.search_dirs.Add(args[i]);
                    }
                }
                else if (args[i] == "--extra-type")
                {
                    i++;
                    if ((i >= args.Length) || (args[i].StartsWith("-")))
                        return false;
                    extra_types.Add(args[i]);
                }
                else if (args[i] == "--extra-typeinfo")
                {
                    i++;
                    if ((i >= args.Length) || (args[i].StartsWith("-")))
                        return false;
                    extra_typeinfos.Add(args[i]);
                }
                else if (args[i] == "--extra-method")
                {
                    i++;
                    if ((i >= args.Length) || (args[i].StartsWith("-")))
                        return false;
                    extra_methods.Add(args[i]);
                }
                else if (args[i] == "-i")
                {
                    interactive = true;
                }
                else if (args[i] == "-q")
                {
                    quiet = true;
                }
                else if (args[i] == "--whole-module")
                {
                    whole_module = true;
                }
                else if (args[i] == "--profile")
                {
                    profile = true;
                }
                else if (args[i] == "-c")
                {
                    whole_module = true;
                    requested_module_only = true;
                }
                else if (args[i].StartsWith("-"))
                    return false;
                else
                {
                    input_file = args[i];
                    FileInfo fi = new FileInfo(input_file);
                    search_dirs.Add(fi.DirectoryName);
                }
                i++;
            }
            if (input_file == null)
                return false;
            return true;
        }

        static string[] GetSupportedArchitectures()
        {
            List<string> ret = new List<string>();

            Type[] ass_types = typeof(Assembler).Assembly.GetTypes();
            foreach (Type t in ass_types)
            {
                if (t.Name.ToLower().EndsWith("_assembler"))
                {
                    if ((t.IsSubclassOf(typeof(Assembler))) && (!t.IsAbstract))
                        ret.Add(t.Name.ToLower().Substring(0, t.Name.ToLower().LastIndexOf("_assembler")));
                }
            }

            return ret.ToArray();
        }
        public static string GetSupportedArchitecturesList()
        {
            StringBuilder sb = new StringBuilder();
            string[] a = GetSupportedArchitectures();
            for (int i = 0; i < a.Length; i++)
            {
                sb.Append(a[i]);
                if (i < (a.Length - 1))
                    sb.Append(", ");
            }
            return sb.ToString();
        }

        static string[] GetSupportedOutputFormats()
        {
            List<string> ret = new List<string>();

            Type[] ass_types = typeof(Assembler).Assembly.GetTypes();
            foreach (Type t in ass_types)
            {
                if (t.Name.ToLower().EndsWith("writer"))
                {
                    List<Type> ints = new List<Type>(t.GetInterfaces());
                    if (ints.Contains(typeof(IOutputFile)) && (!t.IsAbstract))
                        ret.Add(t.Name.ToLower().Substring(0, t.Name.ToLower().LastIndexOf("writer")));
                }
            }

            return ret.ToArray();
        }

        public static string GetSupportedOutputFormatsList()
        {
            StringBuilder sb = new StringBuilder();

            string[] of = GetSupportedOutputFormats();
            for (int i = 0; i < of.Length; i++)
            {
                sb.Append(of[i]);
                if (i < (of.Length - 1))
                    sb.Append(", ");
            }
            return sb.ToString();
        }

        static void ShowHelp()
        {
            Console.WriteLine(bplate);
            Console.WriteLine();
            string cmd_line = Environment.GetCommandLineArgs()[0];
            FileInfo fi = new FileInfo(cmd_line);
            Console.WriteLine("Usage: " + fi.Name + " [options] <input_file>");
            Console.WriteLine();
            Console.WriteLine(" -o <output_file>         generate an object file");
            Console.WriteLine(" --oformat <output_format>");
            Console.WriteLine(" --assembler <architecture>");
            Console.WriteLine(" --output-header <name>   output a C-like header for certain types");
            Console.WriteLine("                           as marked by the ABI.OutputCHeader attribute");
            Console.WriteLine(" --output-cinit <name>    output a C file to initialize certain types");
            Console.WriteLine("                           implies --output-header");
            Console.WriteLine(" --irdump <irdump_file>   dump the intermediate representation to a file");
            Console.WriteLine(" --unimplemented <file>   list all referenced internal calls not provided");
            Console.WriteLine("                           internally in file.  Requires -o");
            Console.WriteLine(" -L <dir>                 add dir to the directory search list for");
            Console.WriteLine("                           references");
			Console.WriteLine(" -d                       increase debug level");
            Console.WriteLine(" -i                       start in interactive mode");
            Console.WriteLine(" --requested-module-only  only compile items in the current module");
            Console.WriteLine(" --whole-module           compile every (non-generic) type and method");
            Console.WriteLine("                           in the module");
            Console.WriteLine(" --exclude <file>         do not compile symbols listed in <file>");
            Console.WriteLine("                           symbols are mangled name with each symbol");
            Console.WriteLine("                           specified on a new line");
            Console.WriteLine(" -c                       compile the whole module, and nothing else");
            Console.WriteLine("                           equivalent to --requested-module-only and");
            Console.WriteLine("                           --whole-module");
            Console.WriteLine(" --profile                emit profiling stubs");
            Console.WriteLine(" -q                       quiet mode");
            Console.WriteLine();
            Console.WriteLine("The following options require <label> to be a mangled name:");
            Console.WriteLine(" --extra-type <label>     request that a particular type is included");
            Console.WriteLine(" --extra-typeinfo <label> request that a particular typeinfo is");
            Console.WriteLine("                           included");
            Console.WriteLine(" --extra-method <label>   request that a particular method is");
            Console.WriteLine("                           included");
            Console.WriteLine();
            Console.Write("Supported output formats: ");
            Console.WriteLine(GetSupportedOutputFormatsList());
            Console.Write("Supported architectures: ");
            Console.WriteLine(GetSupportedArchitecturesList());
            Console.WriteLine();
            Console.WriteLine();
        }
    }
}
